#include <string.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/time.h>

#include "net.h"
#include "quark.h"
#include "model.h"

static void _display_host(struct sockaddr_in *addr) {
  char ip[INET_ADDRSTRLEN];

  memset(ip, 0, sizeof(ip));
  inet_ntop(AF_INET, &addr->sin_addr.s_addr, ip, sizeof(ip));
  printf("%s:%d", ip, ntohs(addr->sin_port));
}

static void _display_addr(struct sockaddr_in *addr) {
  char ip[INET_ADDRSTRLEN];

  memset(ip, 0, sizeof(ip));
  inet_ntop(AF_INET, &addr->sin_addr.s_addr, ip, sizeof(ip));
  printf("%s", ip);
}

static void _display_flow_model_header(flow_model_t *fm) {
  printf("@%ld:%06ld ", fm->relative_start.tv_sec, fm->relative_start.tv_usec);
  if (fm->connector_port == NULL)
    _display_host(&fm->connector);
  else {
    _display_addr(&fm->connector);
    printf(":[var_%03d]", fm->connector_port->id);
  }
  printf(" -> ");
  if (fm->connectee_port == NULL)
    _display_host(&fm->connectee);
  else {
    _display_addr(&fm->connectee);
    printf(":[var_%03d]", fm->connectee_port->id);
  }
  printf(" [%s] ", (fm->protocol == IPPROTO_UDP ? "udp" : "tcp"));
  switch (fm->references[0]->role) {
  case UNKNOWN:
    printf("??");
    break;
  case SENT_BY_US:
    printf("[initiated by us]");
    break;
  case UNICAST_TO_US:
    printf("[initiated by them]");
    break;
  }
  printf("\n");
}

static void _display_escaped_quark(const quark_t *q) {
  char *str;
  int i;

  str = q->segment.iov_base;

  for (i = 0; i < q->segment.iov_len; i++) {
    if (isprint(str[i]))
      printf("%c", str[i]);
    else {
      printf("\\x%02x", (unsigned char) str[i]);
      if (str[i] == '\n')
        printf("\n");
    }
  }
}

int main(int argc, char **argv) {
  flowset_t *sets[2];
  flowset_model_t *model;
  flow_model_t *fm;
  chunked_packets_t *pkts[2];
  packet_model_t *pm;
  chunk_t *c;
  variable_t *var;
  struct timeval current;

  sets[0] = flowset_load(argv[1], (argc == 4 ? argv[3] : NULL));
  sets[1] = flowset_load(argv[2], (argc == 4 ? argv[3] : NULL));
  model = flowset_pair_flows(sets[0], sets[1]);

  pkts[0] = chunked_packets_tokenize(sets[0], 0);
  pkts[1] = chunked_packets_tokenize(sets[1], 0);

  flowset_analyze(model, pkts[0], pkts[1]);

  TAILQ_FOREACH(var,&model->variables,next) {
    if (var->references[1]) {
      printf("variable [var_%03d]: {'%.*s','%.*s'}\n", var->id,
        var->references[0]->segment.iov_len, (char *) var->references[0]->segment.iov_base,
        var->references[1]->segment.iov_len, (char *) var->references[1]->segment.iov_base);
    }
    else {
      printf("variable [var_%03d]: {'%.*s'}\n", var->id,
        var->references[0]->segment.iov_len, (char *) var->references[0]->segment.iov_base);
    }
  }

  TAILQ_FOREACH(fm,&model->flows,next) {
    printf("flow %p ", fm->references[0]);
    _display_flow_model_header(fm);
  }

  TAILQ_FOREACH(pm,&model->packets,next_by_time) {
    timeradd(&pm->relative_start,&pm->flow->relative_start,&current);
    printf("%s packet (flow=%p) @%ld:%06ld ***\n", (pm->references[0]->packet->type == REQUEST_PACKET ? "request" : "response"), pm->references[0]->packet->flow,
      current.tv_sec, current.tv_usec);
    TAILQ_FOREACH(c,&pm->chunks,next) {
      switch (c->type) {
      case CONSTANT:
        _display_escaped_quark(&c->_u.constant_quark);
        break;
      case VARIABLE:
        printf("[var]");
        break;
      case INSTANCE:
        printf("[var_%03d]", c->_u.instance->id);
        break;
      }
    }
    printf("***\n");
  }

  return 0;
}
